package com.et114.components.osworkflow4Ibatis;

import java.io.*;
import java.util.*;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.SAXException;
import com.ibatis.sqlmap.client.SqlMapClient;
import com.opensymphony.module.propertyset.PropertySet;
import com.opensymphony.workflow.loader.*;
import com.opensymphony.workflow.FactoryException;
import com.opensymphony.workflow.FunctionProvider;
/*
 * @author guanhw
 */
public class IBatisWorkflowFactory extends XMLWorkflowFactory implements FunctionProvider {
	private static final long						serialVersionUID	= 8481483873305384443L;
	protected Log log = LogFactory.getLog( this.getClass( ) );
	protected SqlMapClient							sqlMapClient;
	protected static Map							workflows;
	protected boolean								reload;
	protected String                                resource ; 
	
	public void setSqlMapClient ( SqlMapClient sqlMapClient ) {
		this.sqlMapClient = sqlMapClient;
	}
	
	public SqlMapClient getSqlMapClient ( ) {
		return sqlMapClient;
	}
	
	public String getResource ( ) {
		return resource;
	}

	public void setResource ( String resource ) {
		this.resource = resource;
	}

	public boolean isReload ( ) {
		return reload;
	}

	public WorkflowDescriptor getWorkflow ( String name , boolean validate )
			throws FactoryException {
		WfConfig c = ( WfConfig ) workflows.get ( name );
		
		if ( c == null ) {
			throw new RuntimeException ( "Unknown workflow name \"" + name
					+ "\"" );
		}
		
		if ( log.isDebugEnabled ( ) ) {
			log.debug ( "getWorkflow " + name
					+ " descriptor=..to long, no log..." );
		}
		
		if ( c.descriptor != null ) {
			if ( reload ) {
				// @todo check timestamp
				try {
					c.descriptor = load ( c.wfName , validate );
				} catch ( FactoryException e ) {
					throw e;
				} catch ( Exception e ) {
					throw new FactoryException ( "Error reloading workflow" , e );
				}
			}
		} else {
			try {
				c.descriptor = load ( c.wfName , validate );
			} catch ( FactoryException e ) {
				throw e;
			} catch ( Exception e ) {
				throw new FactoryException ( "Error loading workflow" , e );
			}
		}
		
		return c.descriptor;
	}
	
	public String [ ] getWorkflowNames ( ) {
		int i = 0;
		String [ ] res = new String [ workflows.keySet ( ).size ( ) ];
		Iterator it = workflows.keySet ( ).iterator ( );
		
		while (it.hasNext ( )) {
			res [ i ++ ] = ( String ) it.next ( );
		}
		
		return res;
	}
	
	public void execute ( Map transientVars , Map args , PropertySet ps ) {
		String name = ( String ) args.get ( "name" );
		WorkflowDescriptor wfds = ( WorkflowDescriptor ) transientVars
				.get ( "descriptor" );
		
		try {
			saveWorkflow ( name , wfds , false );
		} catch ( Exception e ) {
		}
	}
	
	public void initDone ( ) throws FactoryException {
		try {
			init ( );
			reload = getProperties ( ).getProperty ( "reload" , "false" ).equalsIgnoreCase ( "true" );
			List list = getSqlMapClient ( ).queryForList ( "selectWorkflowConfig" , null );
			log.info ( "select wf defs from db, count: " + list.size ( ) );
			for ( int i = 0 ; i < list.size ( ) ; i ++ ) {
				WorkflowConfigData data = ( WorkflowConfigData ) list.get ( i );
				String name = data.getName ( );
				log.info ( "get wf def, name: " + name );
				WfConfig config = new WfConfig ( name );
				workflows.put ( name , config );
			}
			/*
			Properties props = new Properties ( );
			props.setProperty ( "reload" , String.valueOf ( isReload ( ) ) );
			props.setProperty ( "resource" , getResource ( ) );
			super.init ( props );
            initDone();	
            */		
			//initDone();	
		} catch ( Exception e ) {
			throw new FactoryException (
					"Could not read workflow names from datasource" , e );
		}
	}
	
	public byte [ ] read ( String workflowname ) throws FactoryException {
		byte [ ] wf = new byte [ 0 ];
		try {
			WorkflowConfigData data = new WorkflowConfigData( ) ; 
			data.setName ( workflowname ) ;
			data = ( WorkflowConfigData ) getSqlMapClient ( )
					.queryForObject ( "selectWorkflowConfig" , data );
			wf = data.getDescriptor ( ).getBytes ( );
			log.debug ( "get def config from database, name: " + workflowname
					+ " bytes: " + wf.length );
		} catch ( Exception e ) {
			throw new FactoryException ( "Unable to read workflow: "
					+ e.toString ( ) , e );
		}
		
		return wf;
	}
	
	public boolean removeWorkflow ( String name ) throws FactoryException {
		boolean removed = false;
		
		try {
			int rows = this.getSqlMapClient ( ).delete (
					"deleteWorkflowConfig" , name );
			
			if ( rows == 1 ) {
				removed = true;
				workflows.remove ( name );
			}
		} catch ( Exception e ) {
			throw new FactoryException ( "Unable to remove workflow: "
					+ e.toString ( ) , e );
		}
		
		return removed;
	}
	
	public boolean saveWorkflow ( String name , WorkflowDescriptor descriptor ,
			boolean replace ) throws FactoryException {
		WfConfig c = ( WfConfig ) workflows.get ( name );
		if ( ( c != null ) && ! replace ) {
			return false;
		}
		
		ByteArrayOutputStream bout = new ByteArrayOutputStream ( );
		Writer out = new OutputStreamWriter ( bout );
		
		PrintWriter writer = new PrintWriter ( out );
		writer.println ( WorkflowDescriptor.XML_HEADER );
		writer.println ( WorkflowDescriptor.DOCTYPE_DECL );
		descriptor.writeXML ( writer , 0 );
		writer.flush ( );
		writer.close ( );
		
		// @todo is a backup necessary?
		try {
			return write ( name , bout.toByteArray ( ) );
		} catch ( Exception e ) {
			throw new FactoryException ( "Unable to save workflow: "
					+ e.toString ( ) , e );
		} finally {
			WfConfig config = new WfConfig ( name );
			workflows.put ( name , config );
		}
	}
	
	public boolean write ( String workflowname , byte [ ] wf )
			throws FactoryException {
		boolean written = false;
		try {
			WorkflowConfigData data = new WorkflowConfigData ( );
			data.setName ( workflowname );
			data.setDescriptor ( new String ( wf , "utf-8" ) );
			if ( exists ( workflowname ) ) {
				getSqlMapClient ( ).update ( "updateWorkflowConfig" , data );
			} else {
				getSqlMapClient ( ).update ( "insertWorkflowConfig" , data );
			}
			written = true;
		} catch ( Exception e ) {
			throw new FactoryException ( "Unable to save workflow: "
					+ e.toString ( ) , e );
		}
		return written;
	}
	
	private boolean exists ( String workflowname ) {
		boolean exists = false;
		try {
			WorkflowConfigData data = new WorkflowConfigData( ) ; 
			data.setName ( workflowname ) ; 
			data = ( WorkflowConfigData ) getSqlMapClient ( )
					.queryForObject ( "selectWorkflowConfig" , data );
			if ( data != null ) {
				exists = true;
			}
		} catch ( Exception e ) {
			log.fatal ( "Could not check if [" + workflowname + "] exists" , e );
		}
		return exists;
	}
	
	private void init ( ) {
		workflows = new HashMap ( );
	}
	
	private WorkflowDescriptor load ( final String wfName , boolean validate )
			throws IOException , SAXException , FactoryException {
		byte [ ] wf;
		try {
			wf = read ( wfName );
		} catch ( Exception e ) {
			throw new FactoryException ( "Error loading workflow:" + e , e );
		}
		ByteArrayInputStream is = new ByteArrayInputStream ( wf );
		return WorkflowLoader.load ( is , validate );
	}
	
	public void setReload ( boolean reload ) {
		this.reload = reload;
	}
	
	// ~ Inner Classes
	// //////////////////////////////////////////////////////////
	class WfConfig {
		String				wfName;
		WorkflowDescriptor	descriptor;
		long				lastModified;
		
		public WfConfig ( String name ) {
			wfName = name;
		}
	}
	
}